package main

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"time"

	"github.com/mozillazg/request"
)

type (
	Message struct {
		UserId           int64  `json:"user_id"`
		Nickname         string `json:"nickname"`
		Username         string `json:"username"`
		RequestType      int64  `json:"request_type"`
		ChatId           int64  `json:"chat_id"`
		ChatType         int64  `json:"chat_type"`
		MessageType      int64  `json:"message_type"`
		MessageTimestamp int64  `json:"message_time"`
		UUID             string `json:"uuid"`
		Text             string `json:"text"`
		RawText          string `json:"raw_text"`
		ImageUrl         string `json:"image_url"`
	}
)

const (
	RoomChatType = 0
	UserChatType = 1

	Male          = 1
	Female        = 2
	MaleAndFemale = 3

	TextMessageType         = 0
	PhoneMessageType        = 1
	SoundMessageType        = 2
	CoordinateMessageType   = 3
	SystemMessageType       = 5
	ExpressionMessageType   = 6
	PredefinedMessageType   = 7
	BusinessCardMessageType = 8
	VideoMessageType        = 10
	FileMessageType         = 11
	Mp3MessageType          = 13
	GifMessageType          = 14
	SendTextMessageURL      = "https://botapi.chaoxin.com/sendTextMessage/280168:2b43c3576a5a851286bbe01103d22694"
	SendImageMessageURL     = "https://botapi.chaoxin.com/sendImageMessage/280168:2b43c3576a5a851286bbe01103d22694"
)

var (
	saveAvatarPath string
	genderInfo     = map[int]string{
		Male:          "男",
		Female:        "女",
		MaleAndFemale: "都可以",
	}
	textGenderMap = map[string]int{
		"男的":  Male,
		"女的":  Female,
		"都可以": MaleAndFemale,
	}
)

func receiveMessage(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Printf("failed on ioutil.ReadAll, err: %s\n", err)
		return
	}
	log.Printf("=> data: %v", string(body))
	var msg Message
	if err = json.Unmarshal(body, &msg); err != nil {
		log.Printf("failed on json.Unmarshal, err: %s\n", err)
		return
	}

	if msg.ChatType != UserChatType {
		return
	}

	var uploadAvatar string
	friend, err := getFriendTransaction(msg.ChatId)
	if err != nil {
		log.Printf("failed on getFriendTransaction, err: %s\n", err)
		return
	}

	if msg.Text == "关闭" {
		if err = delFriend(friend.Gender, friend.ChatId); err != nil {
			log.Printf("failed on delFriend, err: %s\n", err)
		}
		return
	}

	switch msg.Text {
	case "所在城市":
		friend.CurrentSetting = "city"
		resp(map[string]string{
			"chat_id":        strconv.FormatInt(msg.ChatId, 10),
			"chat_type":      strconv.FormatInt(msg.ChatType, 10),
			"text":           setCityText,
			"reply_keyboard": settingCancelKeyBoard,
		}, friend, "", true)
		return
	case "更新头像":
		friend.CurrentSetting = "avatar"
		resp(map[string]string{
			"chat_id":        strconv.FormatInt(msg.ChatId, 10),
			"chat_type":      strconv.FormatInt(msg.ChatType, 10),
			"text":           setAvatarText,
			"reply_keyboard": settingCancelKeyBoard,
		}, friend, "", true)
		return
	case "个人介绍":
		friend.CurrentSetting = "introduce"
		resp(map[string]string{
			"chat_id":        strconv.FormatInt(msg.ChatId, 10),
			"chat_type":      strconv.FormatInt(msg.ChatType, 10),
			"text":           setIntroduceText,
			"reply_keyboard": settingCancelKeyBoard,
		}, friend, "", true)
		return
	case "对方性别":
		friend.CurrentSetting = "expectGender"
		resp(map[string]string{
			"chat_id":        strconv.FormatInt(msg.ChatId, 10),
			"chat_type":      strconv.FormatInt(msg.ChatType, 10),
			"text":           setExpectGenderText,
			"reply_keyboard": settingExpectGenderCancelKeyBoard,
		}, friend, "", true)
		return
	case "取消":
		friend.CurrentSetting = ""
		resp(map[string]string{
			"chat_id":        strconv.FormatInt(msg.ChatId, 10),
			"chat_type":      strconv.FormatInt(msg.ChatType, 10),
			"text":           continueSetText,
			"reply_keyboard": settingKeyBoard,
		}, friend, "", true)
	case "返回":
		friend.CurrentSetting = ""
	}

	if msg.Text == "设置" {
		resp(map[string]string{
			"chat_id":        strconv.FormatInt(msg.ChatId, 10),
			"chat_type":      strconv.FormatInt(msg.ChatType, 10),
			"text":           setInfoText,
			"reply_keyboard": settingKeyBoard,
		}, friend, "", true)
		return
	} else {
		switch friend.CurrentSetting {
		case "city":
			goto city
		case "avatar":
			goto avatar
		case "introduce":
			goto introduce
		case "expectGender":
			goto expectGender
		}
	}

	if friend.SetTimestamp != 0 {
		goto end
	}

	switch friend.CurrentStep {
	case "gender":
		goto gender
	case "introduce":
		goto introduce
	case "city":
		goto city
	case "avatar":
		goto avatar
	case "expectGender":
		goto expectGender
	}

	friend.ChatId = msg.ChatId
	friend.Username = msg.Username
	if friend.Username == "" {
		resp(map[string]string{
			"chat_id":        strconv.FormatInt(msg.ChatId, 10),
			"chat_type":      strconv.FormatInt(msg.ChatType, 10),
			"text":           startTipText,
			"reply_keyboard": continueKeyBoard,
		}, friend, "", false)
		return
	}
	friend.CurrentStep = "gender"

gender:
	friend.Gender = textGenderMap[msg.Text]

	if friend.Gender == 0 {
		resp(map[string]string{
			"chat_id":        strconv.FormatInt(msg.ChatId, 10),
			"chat_type":      strconv.FormatInt(msg.ChatType, 10),
			"text":           setGenderText,
			"reply_keyboard": genderKeyBoard,
		}, friend, "", true)
		return
	} else {
		msg.Text = ""
	}
	friend.CurrentStep = "introduce"

introduce:
	friend.Introduce = msg.Text
	if friend.Introduce == "" || friend.CurrentSetting == "introduce" {
		introducePostData := map[string]string{
			"chat_id":   strconv.FormatInt(msg.ChatId, 10),
			"chat_type": strconv.FormatInt(msg.ChatType, 10),
			"text":      setIntroduceText,
		}
		if friend.Introduce != "" && msg.Text != "" {
			uploadAvatar = friend.Avatar
			introducePostData["text"] = fmt.Sprintf(personalInfo, friend.Username, genderDesc[friend.Gender], friend.City, friend.Introduce, genderInfo[friend.ExpectGender], friend.Username)
			introducePostData["reply_keyboard"] = settingKeyBoard
		}
		resp(introducePostData, friend, uploadAvatar, true)
		return
	} else {
		msg.Text = ""
	}
	friend.CurrentStep = "city"

city:
	if friend.City != "" && getCityName(msg.Text) != "" {
		friend.City = getCityName(msg.Text)
	}
	friend.City = getCityName(msg.Text)
	if friend.City == "" || friend.CurrentSetting == "city" {
		cityPostData := map[string]string{
			"chat_id":   strconv.FormatInt(msg.ChatId, 10),
			"chat_type": strconv.FormatInt(msg.ChatType, 10),
			"text":      setCityText,
		}
		if friend.City != "" && getCityName(msg.Text) != "" {
			uploadAvatar = friend.Avatar
			cityPostData["text"] = fmt.Sprintf(personalInfo, friend.Username, genderDesc[friend.Gender], friend.City, friend.Introduce, genderInfo[friend.ExpectGender], friend.Username)
			cityPostData["reply_keyboard"] = settingKeyBoard
		}
		if friend.City != "" && msg.Text != "" && getCityName(msg.Text) == "" {
			cityPostData["text"] = setRightCityText
		}
		resp(cityPostData, friend, uploadAvatar, true)
		return
	}
	friend.CurrentStep = "avatar"

avatar:
	if msg.ImageUrl != "" {
		if friend.Avatar, err = saveAvatar(msg.ImageUrl, msg.ChatId); err != nil {
			log.Printf("failed on saveAvatar, err: %s\n", err)
			return
		}
	}
	if friend.Avatar == "" || friend.CurrentSetting == "avatar" {
		avatarPostData := map[string]string{
			"chat_id":   strconv.FormatInt(msg.ChatId, 10),
			"chat_type": strconv.FormatInt(msg.ChatType, 10),
			"text":      setAvatarText,
		}
		if friend.Avatar != "" && msg.ImageUrl != "" {
			uploadAvatar = friend.Avatar
			avatarPostData["text"] = fmt.Sprintf(personalInfo, friend.Username, genderDesc[friend.Gender], friend.City, friend.Introduce, genderInfo[friend.ExpectGender], friend.Username)
			avatarPostData["reply_keyboard"] = settingKeyBoard
		}
		resp(avatarPostData, friend, uploadAvatar, true)
		return
	}
	friend.CurrentStep = "expectGender"

expectGender:
	friend.ExpectGender = textGenderMap[msg.Text]
	if friend.ExpectGender == 0 || friend.CurrentSetting == "expectGender" {
		expectGenderPostData := map[string]string{
			"chat_id":        strconv.FormatInt(msg.ChatId, 10),
			"chat_type":      strconv.FormatInt(msg.ChatType, 10),
			"text":           setExpectGenderText,
			"reply_keyboard": settingExpectGenderKeyBoard,
		}
		if friend.ExpectGender != 0 && (msg.Text == "男的" || msg.Text == "女的" || msg.Text == "都可以") {
			uploadAvatar = friend.Avatar
			expectGenderPostData["text"] = fmt.Sprintf(personalInfo, friend.Username, genderDesc[friend.Gender], friend.City, friend.Introduce, genderInfo[friend.ExpectGender], friend.Username)
			expectGenderPostData["reply_keyboard"] = settingKeyBoard
		}
		resp(expectGenderPostData, friend, uploadAvatar, true)
		return
	}

	if friend.SetTimestamp == 0 {
		friend.SetTimestamp = msg.MessageTimestamp
		//save into redis
		err = saveRecommendFriends(friend.Gender, msg.ChatId, friend.SetTimestamp)
		if err != nil {
			log.Printf("failed on saveRecommendFriends, err: %s\n", err)
			return
		}

		uploadAvatar = friend.Avatar
		resp(map[string]string{
			"chat_id":   strconv.FormatInt(msg.ChatId, 10),
			"chat_type": strconv.FormatInt(msg.ChatType, 10),
			"text":      fmt.Sprintf(personalInfo, friend.Username, genderDesc[friend.Gender], friend.City, friend.Introduce, genderInfo[friend.ExpectGender], friend.Username),
		}, friend, uploadAvatar, false)

		resp(map[string]string{
			"chat_id":        strconv.FormatInt(msg.ChatId, 10),
			"chat_type":      strconv.FormatInt(msg.ChatType, 10),
			"text":           setDoneText,
			"reply_keyboard": settingDoneKeyBoard,
		}, friend, "", true)
		return
	}

end:

	if msg.Text == "返回" {
		resp(map[string]string{
			"chat_id":        strconv.FormatInt(msg.ChatId, 10),
			"chat_type":      strconv.FormatInt(msg.ChatType, 10),
			"text":           setDoneText,
			"reply_keyboard": settingDoneKeyBoard,
		}, friend, "", true)
		return
	}

	if msg.Text == "找一找" || msg.Text == "/找一找" || msg.Text == "更新" || msg.Text == "下一个" || msg.Text == "男的" || msg.Text == "女的" || msg.Text == "都可以" {
		//get friend from redis
		recfd, err := getRecommendFriend(&friend)

		if err != nil {
			log.Printf("failed on getRecommendFriend, err: %s\n", err)
			return
		}

		if recfd.ChatId != 0 {
			resp(map[string]string{
				"chat_id":        strconv.FormatInt(msg.ChatId, 10),
				"chat_type":      strconv.FormatInt(msg.ChatType, 10),
				"text":           fmt.Sprintf(friendInfo, recfd.Username, genderDesc[recfd.Gender], recfd.City, recfd.Introduce, recfd.Username),
				"reply_keyboard": nextKeyBoard,
			}, friend, recfd.Avatar, true)
			return
		} else {
			resp(map[string]string{
				"chat_id":        strconv.FormatInt(msg.ChatId, 10),
				"chat_type":      strconv.FormatInt(msg.ChatType, 10),
				"text":           noRecordText,
				"reply_keyboard": updateKeyBoard,
			}, friend, "", true)
			return
		}
	}
}

func resp(postData map[string]string, ft FriendTransaction, uploadAvatar string, needSaveFriend bool) {
	var err error
	var url string
	log.Printf("<= data: %v", postData)

	c := new(http.Client)
	req := request.NewRequest(c)
	req.Data = postData

	if uploadAvatar != "" {
		f, err := os.Open(uploadAvatar)
		req.Files = []request.FileField{
			request.FileField{"image", filepath.Base(uploadAvatar), f},
		}
		if err != nil {
			log.Printf("failed on os.Open(), err: %v\n", err)
			return
		}
		url = SendImageMessageURL
	} else {
		url = SendTextMessageURL
	}
	resp, err := req.Post(url)
	result, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		log.Printf("failed on request(), err: %v\n", err)
		return
	}
	log.Printf("=> data: %v", string(result))

	if needSaveFriend {
		if err = saveFriendTransaction(ft); err != nil {
			log.Printf("failed on saveFriendTransaction(), err: %v\n", err)
			return
		}
	}
}

func delFriend(gender int, chatId int64) error {
	var err error
	if err = delFriendTransaction(chatId); err != nil {
		return err
	}
	return delRecommendFriends(gender, chatId)
}

func saveAvatar(avatarUrl string, chatId int64) (string, error) {
	saveAvatarUrl := fmt.Sprintf("%v_%v%v", time.Now().Unix(), chatId, path.Ext(avatarUrl))
	resp, err := http.Get(avatarUrl)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	filePath := saveAvatarPath + saveAvatarUrl
	os.MkdirAll(path.Dir(filePath), os.ModePerm)
	f, err := os.Create(filePath)
	if err != nil {
		return "", err
	}
	_, err = io.Copy(f, resp.Body)
	return filePath, err
}
